\documentclass[a4paper]{article}
\usepackage{xeCJK}
%\setlength\textwidth{180pt}
\usepackage{indentfirst}
\usepackage{pdfpages} 
\usepackage{listings}
\usepackage{color}
\usepackage[svgnames]{xcolor}
\usepackage{tikz}
%\usepackage{minted}
\usepackage{fancybox}
\usetikzlibrary{decorations.pathmorphing} % noisy shapes
\usetikzlibrary{fit}					% fitting shapes to coordinates

\usepackage[left=1.8cm,right=1.8cm,top=2cm,bottom=1.5cm]{geometry}
\setmainfont{DejaVu Sans}
\setCJKmainfont{WenQuanYi Micro Hei}
\setCJKfamilyfont{antony}{WenQuanYi Micro Hei}
%\lstset{ language=C, numbers=left, basicstyle=\small\ttfamily\bfseries,
 % keywordstyle=\color{blue}, commentstyle=\color{comment},
  %stringstyle=\color{red}, frame=shadowbox, breaklines=true,
  %backgroundcolor=\color{lightgray},escapeinside=`` }


\lstset{ %
language=C,                % choose the language of the code
basicstyle=\CJKfamily{antony},       % the size of the fonts that are used for the code
numbers=left,                   % where to put the line-numbers
numberstyle=\footnotesize,      % the size of the fonts that are used for the line-numbers
stepnumber=0,                   % the step between two line-numbers. If it is 1 each line will be numbered
numbersep=5pt,                  % how far the line-numbers are from the code
backgroundcolor=\color{white},  % choose the background color. You must add \usepackage{color}
showspaces=false,               % show spaces adding particular underscores
showstringspaces=false,         % underline spaces within strings
showtabs=false,                 % show tabs within strings adding particular underscores
frame=single,   		% adds a frame around the code
tabsize=2,  		% sets default tabsize to 2 spaces
captionpos=b,   		% sets the caption-position to bottom
breaklines=true,    	% sets automatic line breaking
breakatwhitespace=false,    % sets if automatic breaks should only happen at whitespace
escapeinside={\%}{},          % if you want to add a comment within your code
keywordstyle=\CJKfamily{antony}\color{blue},
identifierstyle=,
%numberstyle=\color[RGB]{0,192,192},
commentstyle=\it\color[RGB]{0,96,96},
backgroundcolor=\color[RGB]{245,245,244},
stringstyle=\rmfamily\slshape\color[RGB]{128,0,0}
}


\definecolor{lightgray}{RGB}{230,230,230}
\definecolor{comment}{RGB}{0,180,100}
\newcommand{\clbox}[1]{\noindent\fcolorbox{black}{Lavender}{\vbox{#1}}}
\newcommand{\qebox}[1]{\noindent\fcolorbox{black}{pink}{\vbox{#1}}}
\newcommand{\imwords}[1]{\textbf{\color{red}#1}}
\newcommand{\ccode}[1]{\setmainfont{DejaVu Sans Mono}\begin{lstlisting}#1\end{lstlisting}\setmainfont{DejaVu Sans}}
\newcommand{\sans}{\setmainfont{DejaVu Sans}}
\newcommand{\mono}{\setmainfont{DejaVu Sans Mono}}
% 非常重要, listings关闭非ASCII字符兼容
\lstset{extendedchars=false}
\title{JOS Lab3}
\author{00848161	谢睿明}
\date{}
\begin{document}
\maketitle
\section*{PART A}
\par{Lab3分为两个部分，第一个部分完成的内容是建立用户进程的环境还有初始化中断向量表。}
\par{JOS使用结构体Env来记录进程的信息：
\mono
  \begin{lstlisting}
    struct Env {
      struct Trapframe env_tf;	// Saved registers
      LIST_ENTRY(Env) env_link;	// Free list link pointers
      envid_t env_id;			// Unique environment identifier
      envid_t env_parent_id;		// env_id of this env's parent
      unsigned env_status;		// Status of the environment
      uint32_t env_runs;		// Number of times environment has run

      // Address space
      pde_t *env_pgdir;		// Kernel virtual address of page dir
      physaddr_t env_cr3;		// Physical address of page dir

    };
  \end{lstlisting}}
\sans
\par{Env与Lab2中记录空闲页的结构十分相似，也是使用了LIST\_ENTRY宏来维护用户进程的链表，还保存了必要的用户进程信息，如父进程、内核地址等等。}
\par{在开始动手之前，先了解一下Lab3在启动JOS时相比Lab2多加入了哪些东西。查看init.c这个文件，可以发现Lab3多了两个初始化的函数env\_init和idt\_init，分别是初始化用户进程表和中断向量表，由于在Lab3中只有一个用户进程，所以在初始化完成之后就运行这个进程，也就是env\_run函数所做的工作。下面先完成用户进程的相关模块。}
\section{Exercise 1}
\clbox{Modify i386\_vm\_init() in kern/pmap.c to allocate and map the envs array. This array consists of exactly NENV instances of the Env structure allocated much like how you allocated the pages array. Also like the pages array, the memory backing envs should also be mapped user read-only at UENVS (defined in inc/memlayout.h) so user processes can read from this array.

You should run your code and make sure check\_boot\_pgdir() succeeds. }
\par{第一个练习总是非常的简单，只需要在之前初始化虚拟内存的程序中加入记录用户进程的表，以及将其映射至对应的线性地址即可。注意到用户是可以访问Env这个数组的，所以需要设置PTE\_U位。}
\mono
\begin{lstlisting}
  int k = NENV * sizeof(struct Env);
  envs = (struct Env *)boot_alloc(k, PGSIZE);
  /////////////////////////////////////////////////////////////////
  k = ROUNDUP(k, PGSIZE);
  boot_map_segment(pgdir, UENVS, k, PADDR(envs), PTE_U| PTE_P);
\end{lstlisting}
\sans
\section{Exercise 2}
\clbox{In the file env.c, finish coding the following functions:

env\_init():
    initialize all of the Env structures in the envs array and add them to the env\_free\_list.

env\_setup\_vm():
    allocate a page directory for a new environment and initialize the kernel portion of the new environment's address space.

segment\_alloc():
    allocates and maps physical memory for an environment

load\_icode():
    you will need to parse an ELF binary image, much like the boot loader already does, and load its contents into the user address space of a new environment.

env\_create():
    allocate an environment with env\_alloc and call load\_icode load an ELF binary into it.

env\_run():
    start a given environment running in user mode.\\

As you write these functions, you might find the new cprintf verb \%e useful -- it prints a description corresponding to an error code. For example,\\

	r = -E\_NO\_MEM;

	panic("env\_alloc: \%e", r);\\

will panic with the message "env\_alloc: out of memory".
}
\par{第二个练习需要建立用户环境，需要设置用户的地址空间，保存寄存器，加载用户程序等一系列工作。在开始之前需要熟悉几个重要的全局变量：
  \begin{itemize}
  \item envs：代表所有用户环境。
  \item curenv：当前用户环境。
  \item env\_free\_list：当前空闲用户环境列表，结构和Lab2中的page\_free\_list相同，为一个双向链表。
  \end{itemize}
}
\begin{enumerate}
\item \par{env\_init():}
\par{这个函数用于初始化env\_free\_list，一开始时所有的环境都是空闲的，所以都将其加入到env\_free\_list中，由于需要倒序加入，所以在这里做一个反向的循环。}
\mono
\begin{lstlisting}
  void
  env_init(void)
  {
	// LAB 3: Your code here.
    memset(envs, 0, sizeof(struct Env)*NENV);
    LIST_INIT(&env_free_list);
    
    int i = 0;
    for(i = NENV - 1; i >= 0; i--)
    {
      envs[i].env_id = 0;
      envs[i].env_status = ENV_FREE;
      LIST_INSERT_HEAD(&env_free_list, &envs[i], env_link);
    }
    return;
}
\end{lstlisting}
\sans


\item 
\par{env\_setup\_vm:}
\par{这个函数用于初始化用户环境的页目录，并且将原页目录中的内核部分复制到新的页目录中。}
\par{显然，首先要分配一个页作为用户环境的页目录，并且设置用户环境里的参数（env\_pgdir,env\_cr3）分别指向新分配页的虚拟地址和物理地址，虚拟地址物理地址和页的转换使用Lab2中定义的宏和函数实现。接着将这个页清空，保证用户地址空间没有任何东西。最后将boot\_pgdir位于内核地址之上的内容复制进新空间内。因为boot\_pgdir已经在Lab2中设置过内核地址之上的内容，所以可以作为模板，将UTOP之上的内容都复制到新页上去。需要注意的就是memmove只复制UTOP之上的内容，而它的大小就是页目录的大小减去UTOP。}
\newpage
\mono
\begin{lstlisting}
  	if ((r = page_alloc(&p)) < 0)
		return r;

    e->env_pgdir = page2kva(p);
    p->pp_ref++;
    e->env_cr3 = page2pa(p);
    memset(e->env_pgdir, 0, PGSIZE);
    //set the va above UTOP be the same
    unsigned int offset = UTOP / (4*1024*1024);
    memmove(e->env_pgdir + offset, boot_pgdir + offset, (NPDENTRIES-PDX(UTOP)) * sizeof(pde_t));
\end{lstlisting}
\sans

\item
\par{segment\_alloc():}
\par{这个函数将一段物理地址映射至某个用户环境的虚拟地址空间中。一开始我直接使用了boot\_segment\_alloc来实现，后来报错。经过检查我发现boot\_map\_segment并没有真正的映射物理页面到虚拟页面，而是将虚拟地址映射到物理地址上，跟我想要实现的功能刚好相反。这里需要使用的函数是page\_insert，这个函数才是将某一物理地址映射到虚拟地址中。最后还需要注意一点，函数的两个参数va和len是没有对齐的，需要分别向下取整和向上取整，对于va就简单的使用ROUNDDOWN即可，可是对于len就不可以简单的使用ROUNDUP了，举个例子：PGSIZE = 4k, va = 3k, len = 6k ,则从va起长度为len的虚拟地址跨越了三个页面，但是如果仅仅将va下取整，len上取整，就丢失了一个页面。所以对len的处理应该如下：首先将len+va对其上取整，之后减去下取整之后的va`，这样才能保证不丢失页。}
\mono
\begin{lstlisting}
  static void
  segment_alloc(struct Env *e, void *va, size_t len)
  {
    void * var = ROUNDDOWN(va, PGSIZE);
    size_t lenr = ROUNDUP(va + len, PGSIZE) - var;
    int i;
    for(i = 0; i < lenr; i += PGSIZE)
    {
      struct Page * p;
      assert(page_alloc(&p) == 0);
      page_insert(e->env_pgdir, p, var + i, PTE_U|PTE_W|PTE_P);
    } 
  }
\end{lstlisting}\sans

\item
\par{load\_icode():}
\par{这个函数将一个ELF文件载入到用户环境中，跟BootLoader中的Main.c文件的功能十分相像。Main.c中直接转入ELF文件中的entry运行，在这里我们将用户环境的eip设为entry，通过后面的env\_run来运行。但是需要注意一个问题：在这个函数中需要使用到memmove这个函数，memmove是操作虚拟地址的，所以需要载入用户环境的页目录，这样才能正确的寻址，函数结束后恢复之前的页目录地址。}
\mono
\begin{lstlisting}
  uint32_t CR3 = rcr3();
  
  struct Elf * elf = (struct Elf *)binary;
  struct Proghdr *ph, *eph;
  if(elf->e_magic != ELF_MAGIC)
  panic("not the magic num!");
  ph = (struct Proghdr *)(binary + elf->e_phoff);
  eph = ph + elf->e_phnum;
  lcr3(e->env_cr3);
  for(; ph < eph; ph++)
  {
    if(ph->p_type == ELF_PROG_LOAD)
    {
      segment_alloc(e, (void *)ph->p_va, ph->p_memsz);
      memmove((void *)ph->p_va, (const void *)binary + ph->p_offset, (size_t)ph->p_filesz);      
      memset((void *)(ph->p_va + ph->p_filesz),0, ph->p_memsz - ph->p_filesz);
    }
  }
  e->env_tf.tf_eip = elf->e_entry;
  segment_alloc(e, (void *)(USTACKTOP-PGSIZE), PGSIZE);  
  lcr3(CR3);
\end{lstlisting}\sans

\item 
\par{env\_create:}
\par{这个函数调用env\_alloc和load\_icode来建立一个新的用户环境。注意需要将parentid设为0。}
\item
\par{env\_run:}
\par{这个函数实现进程上下文的切换，如果当前进程和即将运行的进程不一样，则需要将curenv置为当前进程的env，并增加env\_runs的值。然后载入当前进程的页目录，最后调用env\_pop\_tf函数，即运行该用户进程。}
\par{这个函数本身没有难度，这里需要了解一下env\_pop\_tf这个函数是如何工作的。}\mono
\begin{lstlisting}
	__asm __volatile("movl %0,%%esp\n"
		"\tpopal\n"
		"\tpopl %%es\n"
		"\tpopl %%ds\n"
		"\taddl \$0x8,%%esp\n" /* skip tf_trapno and tf_errcode */
		"\tiret"
		: : "g" (tf) : "memory");
	panic("iret failed");  /* mostly to placate the compiler */
\end{lstlisting}\sans
\par{这个函数其实就是模拟了中断调用返回时的“iret”指令。第一句将第一个参数（tf）的地址移到esp中，这样就改变了栈顶的的值，esp指向了之前设置好的tf。接着一系列的pop语句，将tf中设置好的值一一移入对应的寄存器中。最后执行iret，即得到用户环境的esp和eip，即执行用户程序。}
\par{现在，程序的执行顺序如下：}
\begin{itemize}
\item start
\item i386\_init
  \begin{itemize}
  \item cons\_init
  \item i386\_detect\_memory
  \item i386\_vm\_init
  \item page\_init
  \item env\_init
  \item idt\_init(incomplete)
  \item env\_create
  \item env\_run
    \begin{itemize}
    \item env\_pop\_tf
    \end{itemize}
  \end{itemize}
\end{itemize}
\end{enumerate}
\par{至此完成了用户环境部分，接下来将是中断/异常处理的部分。}
\section{Exercise 3}
\clbox{Read Chapter 9, Exceptions and Interrupts in the 80386 Programmer's Manual (or Chapter 5 of the IA-32 Developer's Manual), if you haven't already. }
\par{Done.}
\section{Exercise 4}
\clbox{Edit trapentry.S and trap.c and implement the features described above. The macros TRAPHANDLER and TRAPHANDLER\_NOEC in trapentry.S should help you, as well as the T\_* defines in inc/trap.h. You will need to add an entry point in trapentry.S (using those macros) for each trap defined in inc/trap.h, and you'll have to provide \_alltraps which the TRAPHANDLER macros refer to. You will also need to modify idt\_init() to initialize the idt to point to each of these entry points defined in trapentry.S; the SETGATE macro will be helpful here.

Your \_alltraps should:

   1. push values to make the stack look like a struct Trapframe

   2. load GD\_KD into \%ds and \%es

   3. pushl \%esp to pass a pointer to the Trapframe as an argument to trap()

   4. call trap (can trap ever return?)


Consider using the pushal instruction; it fits nicely with the layout of the struct Trapframe.

Test your trap handling code using some of the test programs in the user directory that cause exceptions before making any system calls, such as user/divzero. You should be able to get make grade to succeed on the divzero, softint, and badsegment tests at this point.
}
\par{Exercise4需要实现IDT的初始化，即让每一种中断都有一个对应的入口点。当中断发生时，JOS首先寻找中断向量表（IDT），寻找对应的中断程序的入口，然后根据入口调用适当的中断处理程序来处理这个中断。在trapentry.S的文件对应了每一个中断程序入口，在这个程序中最终调用的是trap.c中的trap函数，注意到trap函数有一个类型为Trapframe的函数，所以需要在调用trap函数之前要将对应的信息推栈，让栈中形成一个Trapframe的结构。Trapframe是一个struct型的数据。struct中的变量在栈中的位置是这样的：在struct定义越后的变量在栈中的地址越高。所以将变量推栈时应该从后定义的变量开始一直到第一个定义的变量。}
\par{首先修改trapentry.S文件，查询Intel中断手册可以获得每一个中断是否需要error code。如果需要error code，则使用TRAPHANDLER宏，否则使用另外一个。对应每一个中断的编号都使用一次宏。}
\par{由于每一个宏最终都要跳转至，\_alltraps这个标签处，这个标签需要做的工作是载入代码段，接着推栈，最后调用trap.c中的trap函数来运行中断处理函数。这里需要注意的就是推栈的顺序，应该按照变量定义的先后逆序推栈。当然首先推ds，其次es，接着就使用pushal，刚好将剩下的寄存器都推入栈中。当然，调用了trap之后还需要返回，同样的出栈最后使用iret跳转。}
\par{接下来是trap.c文件，在这里需要修改idt\_init函数，为每一个中断设定一个门描述符。在这里使用的是SETGATE宏，这个宏接受5个输入，分别代表门描述符地址、是否为陷阱门、代码段选择子、入口程序偏移量、权限。其中陷阱门可以由用户调用，权限为3。其余其他门运行在内核态，权限为0。代码段选择子则全部为GD\_KT，代表内核代码段。偏移量就是在trapentry.S文件中为每一个中断定义的全局标号。查询Intel手册同样可以知道只有系统调用和断点这两种中断属于陷阱，其余都不属于。所以就根据在.S文件定义的门在trap.c中加入同样的SETGATE语句。}


\qebox{Challenge! You probably have a lot of very similar code right now, between the lists of TRAPHANDLER in trapentry.S and their installations in trap.c. Clean this up. Change the macros in trapentry.S to automatically generate a table for trap.c to use. Note that you can switch between laying down code and data in the assembler by using the directives .text and .data. }
\par{这个Challenge需要修改trapentry.S文件，使得在trap.c文件中不用枚举每一个中断向量，而可以采取一种比较“自动”的手段来初始化门描述符。因为汇编文件可以用.data和.text区分代码段和数据段，.text内的内容不算入数据段中。所以考虑在trapentry.S中，代码部分前都加上.text，数据前都加上.data，而且在宏定义中加入代表name和num的数据。在开头定义一个全局的标号，作为数据的起始，在最后设定结尾的标志（我采用两个0）。这样，在trap.c中就可以通过循环来初始化门描述符了。}

\mono
\begin{lstlisting}[language={[x86masm]Assembler}]
  #define TRAPHANDLER_NOEC(name, num)					\
  .text							;\
  .globl name;							\
  .type name, @function;						\
  .align 2;							\
  name:								\
  pushl $0;							\
  pushl $(num);							\
  jmp _alltraps				;\
  .data					;\
  .long num,name  
  .data
  .globl entry
  entry:	
\end{lstlisting}
\sans
\newpage
\mono
\begin{lstlisting}
//new trap.c
  extern long entry[][2];
  for(i = 0; entry[i][1]!= 0; i++)
  {
    if((entry[i][0] == T_SYSCALL) || (entry[i][0] == T_BRKPT))
    {
      SETGATE(idt[entry[i][0]], 1, GD_KT, entry[i][1], 3);
    }
    else
    {
      SETGATE(idt[entry[i][0]], 0, GD_KT, entry[i][1], 0);
    }
  }
\end{lstlisting}
\sans

\clbox{Questions

   1. What is the purpose of having an individual handler function for each exception/interrupt? (i.e., if all exceptions/interrupts were delivered to the same handler, what feature that exists in the current implementation could not be provided?)

   2. Did you have to do anything to make the user/softint program behave correctly? The grade script expects it to produce a general protection fault (trap 13), but softint's code says int \$14. Why should this produce interrupt vector 13? What happens if the kernel actually allows softint's int \$14 instruction to invoke the kernel's page fault handler (which is interrupt vector 14)?
}
\par{1.因为现在中断的机制还无法识别不同类型的中断，如果都使用一个函数解决，那么将无法区分不同的中断（因为函数内部不知道中断号）。}
\par{2.Page Fault不是一个用户能够调用的中断，所以当用户尝试调用这个中断时，就会发生保护错误。如果允许用户调用，因为Page Fault需要压入error code，而用户没有压入，导致栈的结构被破坏。}
\section*{PART B}
\par{至此，系统实现了对异常/中断处理的初始化，即遇到一个中断就可以跳到对应的中断处理程序的入口，可是中断处理程序我们还没有实现，现在需要对一些具体的中断进行处理。}
\section{Exercise 5}
\clbox{Modify trap\_dispatch() to dispatch page fault exceptions to page\_fault\_handler(). You should now be able to get make grade to succeed on the faultread, faultreadkernel, faultwrite, and faultwritekernel tests. If any of them don't work, figure out why and fix them. Remember that you can boot JOS into a particular user program using make run-x or make run-x-nox. }
\par{这个练习实现对页错误的处理。在trap\_dispatch函数中，参数为Trapframe *tf，参考之前宏的推栈顺序得到，这个结构的tf\_trapno就是错误码，所以当错误码等于T\_PGFLT时，就将这个tf作为参数调用page\_fault\_handler即可。}
\section{Exercise 6}
\clbox{Modify trap\_dispatch() to make breakpoint exceptions invoke the kernel monitor. You should now be able to get make grade to succeed on the breakpoint test. }
\par{与上一个练习同理。}

\qebox{Challenge! Modify the JOS kernel monitor so that you can 'continue' execution from the current location (e.g., after the int3, if the kernel monitor was invoked via the breakpoint exception), and so that you can single-step one instruction at a time. You will need to understand certain bits of the EFLAGS register in order to implement single-stepping.

Optional: If you're feeling really adventurous, find some x86 disassembler source code - e.g., by ripping it out of QEMU, or out of GNU binutils, or just write it yourself - and extend the JOS kernel monitor to be able to disassemble and display instructions as you are stepping through them. Combined with the symbol table loading from lab 2, this is the stuff of which real kernel debuggers are made.}
\par{EFLAGS的第八位为TF位，如果设置为1，则每一步都产生一个中断，为DEBUG中断。需要一条一条语句执行，首先要将TF位设置为1。在monitor.c中加入一个函数continue，这个函数首先将当前环境(curenv)的EFLAGS的TF位置为1，然后调用env\_run函数。可想而知这引起了一个DEBUG中断，所以在trap.c中的trap\_dispatch函数中需要加入DEBUG的情况，因为cs:eip指向的是下一条指令的地址，所以不需要人工的改变它，因为每一次执行的都是新的语句。而Lab1中我们曾经通过Stab来打印出指令对应文件、对应函数和对应的偏移量，在这里我们也使用这个方法，只是这里的eip不是通过ebp获得，而是通过tf\_eip获得。打印相应信息后继续调用monitor函数，这样跟刚才相比往前就多执行了一步，至于怎么获得对应的汇编指令，我还没有想到。}

\clbox{Questions

   3. The break point test case will either generate a break point exception or a general protection fault depending on how you initialized the break point entry in the IDT (i.e., your call to SETGATE from idt\_init). Why? How did you need to set it in order to get the breakpoint exception to work as specified above?

   4. What do you think is the point of these mechanisms, particularly in light of what the user/softint test program does?
}
\par{3.将dpl设为3，则不会产生通用保护错误。因为dpl=3意味着用户可以通过int调用，如果dpl=0则用户不可以通过int调用，就会导致发生通用保护错误。}
\par{4.设置权限，让用户只能产生部分中断，将中断处理交给内核，用户如果访问了没有权限的地址就直接陷入内核进行处理，保证了内核的安全。}
\section{Exercise 7}
\par{这个练习实现系统调用（SYSCALL），同样是修改trap\_dispatch。注意到lib/syscall.c的内联汇编中说明将a1、a2、a3、a4、a5分别放入 EDX， ECX， EBX， EDI， ESI这五个寄存器中，所以在trap\_dispatch处理syscall时，就要按照同样的参数顺序传参，并将返回结果输出到EAX之中。}
\par{完成了trap.c中的工作后，下面实现kern/syscall.c，根据syscall.h的系统调用号分别对应不同的处理函数即可。}\mono

\begin{lstlisting}
  int32_t
  syscall(uint32_t syscallno, uint32_t a1, uint32_t a2, uint32_t a3, uint32_t a4, uint32_t a5)
  {
	// Call the function corresponding to the 'syscallno' parameter.
	// Return any appropriate return value.
	// LAB 3: Your code here.
    switch(syscallno)
    {
      case SYS_cputs:
      sys_cputs((const char*)a1, (size_t)a2);
      return 0;
      case SYS_env_destroy:
      sys_env_destroy(a1);
      case SYS_getenvid:
      sys_getenvid();
      return 0;
      default:
      return -E_INVAL;
    }
    
    
    //    panic("syscall not legal");
    return -E_INVAL;
  }
\end{lstlisting}\sans

\section{Exercise 8}
\clbox{Add the required code to the user library, then boot your kernel. You should see user/hello print "hello, world" and then print "i am environment 00000800". user/hello then attempts to "exit" by calling sys\_env\_destroy() (see lib/libmain.c and lib/exit.c). Since the kernel currently only supports one user environment, it should report that it has destroyed the only environment and then drop into the kernel monitor. You should be able to get make grade to succeed on the hello test. }
\par{env = \&envs[ENVX(sys\_getenvid())];}
\section{Exercise 9}
\clbox{Change kern/trap.c to panic if a page fault happens in kernel mode.

Hint: to determine whether a fault happened in user mode or in kernel mode, check the low bits of the tf\_cs.

Read user\_mem\_assert in kern/pmap.c and implement user\_mem\_check in that same file.

Change kern/syscall.c to sanity check arguments to system calls.

Boot your kernel, running user/buggyhello. The environment should be destroyed, and the kernel should not panic. You should see:

	[00001000] user\_mem\_check assertion failure for va 00000001

	[00001000] free env 00001000

	Destroyed the only environment - nothing more to do!
	

Finally, change debuginfo\_eip in kern/kdebug.c to call user\_mem\_check on usd, stabs, and stabstr. If you now run user/breakpoint, you should be able to run backtrace from the kernel monitor and see the backtrace traverse into lib/libmain.c before the kernel panics with a page fault. What causes this page fault? You don't need to fix it, but you should understand why it happens. }
\par{首先判断页错误是内核引发的还是用户引发的，这通过tf\_cs判断。在env\_alloc中，用户态的tf\_cs的低三位被置为1，所以就可以通过这个判断，如果低三位为0，则是内核态。如果内核态发生页错误则直接panic。}
\par{user\_mem\_check函数接受四个输入：环境，起始地址，长度，权限。判断从起始地址开始的若干长度的地址空间是否有对应的权限。}
\par{首先还是对地址和长度上下取整页对齐，接着对每一个页地址使用page\_lookup得到对应的页，如果页不存在，或者权限不满足，则将这个user\_mem\_check\_addr设为这个地址，注意user\_mem\_check\_addr至少大于等于va，如果对va进行下取整则需要注意取整之后的va`和va的大小关系。}
\begin{lstlisting}  
  int user_mem_check(struct Env *env, const void *va, size_t len, int perm)
  {
    void * val = ROUNDDOWN((void *)va, PGSIZE);
    size_t lenl = ROUNDUP( va + len, PGSIZE) - val;
    size_t i = (size_t)val;
    for(; i < (size_t)val + lenl; i += PGSIZE)
    {
      if( i > ULIM ) //below ULIM
      {
        //cprintf("hello\n");
        user_mem_check_addr = i;
        if(i < (size_t)va)
        user_mem_check_addr = (size_t)va;
        return -E_FAULT;
      }
      pte_t * pte;
      if(page_lookup(env->env_pgdir, (void *)i, &pte) == NULL)
      {//not exist
        user_mem_check_addr = i;
        if(i < (size_t)va)
        user_mem_check_addr = (size_t)va;
        return -E_FAULT;
      }
      if((*pte & (perm | PTE_P)) >= (perm | PTE_P)) //permission failed
      continue;
      else
      {
        user_mem_check_addr = i;
        if(i < (size_t)va)
        user_mem_check_addr = (size_t)va;
        return -E_FAULT;
      }
      
    }
    
	return 0;
  }
\end{lstlisting}
\par{接着需要在syscall.c中检查访问的地址是否合法，目前只能是syy\_cputs函数，在函数执行之前使用user\_mem\_assert来检查对应内存是否有用户权限和写权限。}
\par{最后，需要修改debuginfo\_eip函数，对于这个函数里访问的三块地址进行检查，分别是usd、stabs和stabstr，就是三个简单的user\_mem\_check函数，唯一需要说明的就是在检查stabs时，由于这个指针是struct Stab*型的，得到正确的大小需要先将其转换成整数再进行减法，否则得到的就是一个偏移量，是错误的结果。}
\begin{lstlisting}
  if(user_mem_check(curenv, (void *)usd , sizeof(struct UserStabData) , PTE_U))
  return -1;
  stabs = usd->stabs;
  stab_end = usd->stab_end;
  stabstr = usd->stabstr;
  stabstr_end = usd->stabstr_end;

  // Make sure the STABS and string table memory is valid.
  // LAB 3: Your code here.
  if (user_mem_check(curenv, stabs, 
  (uintptr_t)stab_end - (uintptr_t)stabs, PTE_U)
  || user_mem_check(curenv, stabstr, stabstr_end - stabstr, PTE_U)) 
  return -1 ;
\end{lstlisting}
\par{缺页中断的原因在于:访问到了内核栈的栈底，栈底之上的空间没有映射，所以出现错误。}
\section{Exercise 10}
\clbox{Boot your kernel, running user/evilhello. The environment should be destroyed, and the kernel should not panic. You should see:

	[00000000] new env 00001000

	[00001000] user\_mem\_check assertion failure for va f0100020

	[00001000] free env 00001000

	}
\par{这个错误的原因同理，因为访问的地址为内核地址，用户没有权限，所以出错。}

\end{document}




